import * as Util from './Util';

// @class Class
// @aka L.Class

// @section
// @uninheritable

// Thanks to John Resig and Dean Edwards for inspiration!

export function Class() {}

Class.extend = function (props) {
  // @function extend(props: Object): Function
  // [Extends the current class](#class-inheritance) given the properties to be included.
  // Returns a Javascript function that is a class constructor (to be called with `new`).
  var NewClass = function () {
    Util.setOptions(this);

    // call the constructor
    if (this.initialize) {
      this.initialize.apply(this, arguments);
    }

    // call all constructor hooks
    this.callInitHooks();
  };

  var parentProto = (NewClass.__super__ = this.prototype);

  var proto = Util.create(parentProto);
  proto.constructor = NewClass;

  NewClass.prototype = proto;

  // inherit parent's statics
  for (var i in this) {
    if (
      Object.prototype.hasOwnProperty.call(this, i) &&
      i !== 'prototype' &&
      i !== '__super__'
    ) {
      NewClass[i] = this[i];
    }
  }

  // mix static properties into the class
  if (props.statics) {
    Util.extend(NewClass, props.statics);
  }

  // mix includes into the prototype
  if (props.includes) {
    checkDeprecatedMixinEvents(props.includes);
    Util.extend.apply(null, [proto].concat(props.includes));
  }

  // mix given properties into the prototype
  Util.extend(proto, props);
  delete proto.statics;
  delete proto.includes;

  // merge options
  if (proto.options) {
    proto.options = parentProto.options ? Util.create(parentProto.options) : {};
    Util.extend(proto.options, props.options);
  }

  proto._initHooks = [];

  // add method for calling all hooks
  proto.callInitHooks = function () {
    if (this._initHooksCalled) {
      return;
    }

    if (parentProto.callInitHooks) {
      parentProto.callInitHooks.call(this);
    }

    this._initHooksCalled = true;

    for (var i = 0, len = proto._initHooks.length; i < len; i++) {
      proto._initHooks[i].call(this);
    }
  };

  return NewClass;
};

// @function include(properties: Object): this
// [Includes a mixin](#class-includes) into the current class.
Class.include = function (props) {
  var parentOptions = this.prototype.options;
  Util.extend(this.prototype, props);
  if (props.options) {
    this.prototype.options = parentOptions;
    this.mergeOptions(props.options);
  }
  return this;
};

// @function mergeOptions(options: Object): this
// [Merges `options`](#class-options) into the defaults of the class.
Class.mergeOptions = function (options) {
  Util.extend(this.prototype.options, options);
  return this;
};

// @function addInitHook(fn: Function): this
// Adds a [constructor hook](#class-constructor-hooks) to the class.
Class.addInitHook = function (fn) {
  // (Function) || (String, args...)
  var args = Array.prototype.slice.call(arguments, 1);

  var init =
    typeof fn === 'function'
      ? fn
      : function () {
          this[fn].apply(this, args);
        };

  this.prototype._initHooks = this.prototype._initHooks || [];
  this.prototype._initHooks.push(init);
  return this;
};

function checkDeprecatedMixinEvents(includes) {
  /* global L: true */
  if (typeof L === 'undefined' || !L || !L.Mixin) {
    return;
  }

  includes = Util.isArray(includes) ? includes : [includes];

  for (var i = 0; i < includes.length; i++) {
    if (includes[i] === L.Mixin.Events) {
      console.warn(
        'Deprecated include of L.Mixin.Events: ' +
          'this property will be removed in future releases, ' +
          'please inherit from L.Evented instead.',
        new Error().stack,
      );
    }
  }
}
